/*
 * Copyright 2020 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkContourMeasure.h"
#include "include/core/SkPathBuilder.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/animator/Animator.h"
#include "modules/skottie/src/animator/KeyframeAnimator.h"

#include <cmath>

namespace skottie::internal {
namespace {
// Spatial 2D specialization: stores SkV2s and optional contour interpolators externally.
class Vec2KeyframeAnimator final : public KeyframeAnimator {
public:
    struct SpatialValue {
        Vec2Value v2;
        sk_sp<SkContourMeasure> cmeasure;
    };

    Vec2KeyframeAnimator(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms, std::vector<SpatialValue> vs,
        Vec2Value *vec_target, float *rot_target)
        : INHERITED(std::move(kfs), std::move(cms)),
          fValues(std::move(vs)),
          fVecTarget(vec_target),
          fRotTarget(rot_target)
    {}

private:
    StateChanged update(const Vec2Value &new_vec_value, const Vec2Value &new_tan_value)
    {
        auto changed = (new_vec_value != *fVecTarget);
        *fVecTarget = new_vec_value;

        if (fRotTarget) {
            const auto new_rot_value = SkRadiansToDegrees(std::atan2(new_tan_value.y, new_tan_value.x));
            changed |= new_rot_value != *fRotTarget;
            *fRotTarget = new_rot_value;
        }

        return changed;
    }

    StateChanged onSeek(float t) override
    {
        auto get_lerp_info = [this](float t) {
            auto lerp_info = this->getLERPInfo(t);

            // When tracking rotation/orientation, the last keyframe requires special handling:
            // it doesn't store any spatial information but it is expected to maintain the
            // previous orientation (per AE semantics).
            //
            // The easiest way to achieve this is to actually swap with the previous keyframe,
            // with an adjusted weight of 1.
            const auto vidx = lerp_info.vrec0.idx;
            if (fRotTarget && vidx == fValues.size() - 1 && vidx > 0) {
                SkASSERT(!fValues[vidx].cmeasure);
                SkASSERT(lerp_info.vrec1.idx == vidx);

                // Change LERPInfo{0, SIZE - 1, SIZE - 1}
                // to     LERPInfo{1, SIZE - 2, SIZE - 1}
                lerp_info.weight = 1;
                lerp_info.vrec0 = { vidx - 1 };

                // This yields equivalent lerp results because keyframed values are contiguous
                // i.e frame[n-1].end_val == frame[n].start_val.
            }

            return lerp_info;
        };

        const auto lerp_info = get_lerp_info(t);

        const auto &v0 = fValues[lerp_info.vrec0.idx];
        if (v0.cmeasure) {
            // Spatial keyframe: the computed weight is relative to the interpolation path
            // arc length.
            SkPoint pos;
            SkVector tan;
            if (v0.cmeasure->getPosTan(lerp_info.weight * v0.cmeasure->length(), &pos, &tan)) {
                return this->update({ pos.fX, pos.fY }, { tan.fX, tan.fY });
            }
        }

        const auto &v1 = fValues[lerp_info.vrec1.idx];
        const auto tan = v1.v2 - v0.v2;

        return this->update(Lerp(v0.v2, v1.v2, lerp_info.weight), tan);
    }

    const std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
    Vec2Value *fVecTarget;
    float *fRotTarget;

    using INHERITED = KeyframeAnimator;
};

class Vec2ExpressionAnimator final : public Animator {
public:
    Vec2ExpressionAnimator(sk_sp<ExpressionEvaluator<std::vector<float>>> expression_evaluator, Vec2Value *target_value)
        : fExpressionEvaluator(std::move(expression_evaluator)), fTarget(target_value)
    {}

private:
    StateChanged onSeek(float t) override
    {
        auto old_value = *fTarget;

        std::vector<float> result = fExpressionEvaluator->evaluate(t);
        fTarget->x = result.size() > 0 ? result[0] : 0;
        fTarget->y = result.size() > 1 ? result[1] : 0;

        return *fTarget != old_value;
    }

    sk_sp<ExpressionEvaluator<std::vector<float>>> fExpressionEvaluator;
    Vec2Value *fTarget;
};

class Vec2AnimatorBuilder final : public AnimatorBuilder {
public:
    Vec2AnimatorBuilder(Vec2Value *vec_target, float *rot_target)
        : INHERITED(Keyframe::Value::Type::kIndex), fVecTarget(vec_target), fRotTarget(rot_target)
    {}

    sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder &abuilder, const skjson::ArrayValue &jkfs) override
    {
        SkASSERT(jkfs.size() > 0);

        fValues.reserve(jkfs.size());
        if (!this->parseKeyframes(abuilder, jkfs)) {
            return nullptr;
        }
        fValues.shrink_to_fit();

        return sk_sp<Vec2KeyframeAnimator>(
            new Vec2KeyframeAnimator(std::move(fKFs), std::move(fCMs), std::move(fValues), fVecTarget, fRotTarget));
    }

    sk_sp<Animator> makeFromExpression(ExpressionManager &em, const char *expr) override
    {
        sk_sp<ExpressionEvaluator<std::vector<SkScalar>>> expression_evaluator =
            em.createArrayExpressionEvaluator(expr);
        return sk_make_sp<Vec2ExpressionAnimator>(expression_evaluator, fVecTarget);
    }

    bool parseValue(const AnimationBuilder &, const skjson::Value &jv) const override
    {
        return Parse(jv, fVecTarget);
    }

private:
    void backfill_spatial(const Vec2KeyframeAnimator::SpatialValue &val)
    {
        SkASSERT(!fValues.empty());
        auto &prev_val = fValues.back();
        SkASSERT(!prev_val.cmeasure);

        if (val.v2 == prev_val.v2) {
            // spatial interpolation only make sense for noncoincident values
            return;
        }

        // Check whether v0 and v1 have the same direction AND ||v0||>=||v1||
        auto check_vecs = [](const SkV2 &v0, const SkV2 &v1) {
            const auto v0_len2 = v0.lengthSquared(), v1_len2 = v1.lengthSquared();

            // check magnitude
            if (v0_len2 < v1_len2) {
                return false;
            }

            // v0, v1 have the same direction iff dot(v0,v1) = ||v0||*||v1||
            // <=>    dot(v0,v1)^2 = ||v0||^2 * ||v1||^2
            const auto dot = v0.dot(v1);
            return SkScalarNearlyEqual(dot * dot, v0_len2 * v1_len2);
        };

        if (check_vecs(val.v2 - prev_val.v2, fTo) && check_vecs(prev_val.v2 - val.v2, fTi)) {
            // Both control points lie on the [prev_val..val] segment
            //   => we can power-reduce the Bezier "curve" to a straight line.
            return;
        }

        // Finally, this looks like a legitimate spatial keyframe.
        SkPathBuilder p;
        p.moveTo(prev_val.v2.x, prev_val.v2.y);
        p.cubicTo(prev_val.v2.x + fTo.x, prev_val.v2.y + fTo.y, val.v2.x + fTi.x, val.v2.y + fTi.y, val.v2.x, val.v2.y);
        prev_val.cmeasure = SkContourMeasureIter(p.detach(), false).next();
    }

    bool parseKFValue(const AnimationBuilder &, const skjson::ObjectValue &jkf, const skjson::Value &jv,
        Keyframe::Value *v) override
    {
        Vec2KeyframeAnimator::SpatialValue val;
        if (!Parse(jv, &val.v2)) {
            return false;
        }

        if (fPendingSpatial) {
            this->backfill_spatial(val);
        }

        // Track the last keyframe spatial tangents (checked on next parseValue).
        fTi = ParseDefault<SkV2>(jkf["ti"], { 0, 0 });
        fTo = ParseDefault<SkV2>(jkf["to"], { 0, 0 });
        fPendingSpatial = fTi != SkV2{ 0, 0 } || fTo != SkV2{ 0, 0 };

        if (fValues.empty() || val.v2 != fValues.back().v2 || fPendingSpatial) {
            fValues.push_back(std::move(val));
        }

        v->idx = SkToU32(fValues.size() - 1);

        return true;
    }

    std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
    Vec2Value *fVecTarget; // required
    float *fRotTarget;     // optional
    SkV2 fTi{ 0, 0 }, fTo{ 0, 0 };
    bool fPendingSpatial = false;

    using INHERITED = AnimatorBuilder;
};
} // namespace

bool AnimatablePropertyContainer::bindAutoOrientable(const AnimationBuilder &abuilder, const skjson::ObjectValue *jprop,
    Vec2Value *v, float *orientation)
{
    if (!jprop) {
        return false;
    }

    if (const auto *sid = ParseSlotID(jprop)) {
        fHasSlotID = true;
        abuilder.fSlotManager->trackVec2Value(SkString(sid->begin()), v, sk_ref_sp(this));
    }

    if (!ParseDefault<bool>((*jprop)["s"], false)) {
        // Regular (static or keyframed) 2D value.
        Vec2AnimatorBuilder builder(v, orientation);
        return this->bindImpl(abuilder, jprop, builder);
    }

    // Separate-dimensions vector value: each component is animated independently.
    bool boundX = this->bind(abuilder, (*jprop)["x"], &v->x);
    bool boundY = this->bind(abuilder, (*jprop)["y"], &v->y);
    return boundX || boundY;
}

template <>
bool AnimatablePropertyContainer::bind<Vec2Value>(const AnimationBuilder &abuilder, const skjson::ObjectValue *jprop,
    Vec2Value *v)
{
    return this->bindAutoOrientable(abuilder, jprop, v, nullptr);
}
} // namespace skottie::internal
